//SPDX-FileCopyrightText: Copyright 2022-2024 深圳市同心圆网络有限公司
//SPDX-License-Identifier: GPL-3.0-only

use git2::{Delta, Diff, Error, Oid, Repository};
use serde::Serialize;
use std::path::PathBuf;

#[derive(Serialize)]
struct FileDiffInfo {
    old_file_name: String,
    old_content: String,
    new_file_name: String,
    new_content: String,
    delta_type: String,
}

fn dump_diff(repo: &Repository, diff: Diff) -> Result<(), Error> {
    let stats = diff.stats();
    if stats.is_err() {
        return Err(stats.err().unwrap());
    }
    let stats = stats.unwrap();
    let mut info_list = Vec::new();
    for index in 0..stats.files_changed() {
        let delta = diff.get_delta(index);
        if delta.is_none() {
            continue;
        }
        let delta = delta.unwrap();
        let new_file = delta.new_file();
        let new_file_name = match new_file.path() {
            Some(path) => String::from(path.to_str().unwrap_or_default()),
            None => String::from(""),
        };
        let new_file = repo.find_blob(new_file.id());
        if new_file.is_err() {
            continue;
        }
        let new_file = new_file.unwrap();
        let mut new_content = String::from("");
        if !new_file.is_binary() {
            new_content = String::from_utf8_lossy(new_file.content()).to_string();
        }
        let mut old_content = String::from("");
        let mut old_file_name = String::from("");
        if delta.nfiles() == 2 {
            let old_file = delta.old_file();
            old_file_name = match old_file.path() {
                Some(path) => String::from(path.to_str().unwrap_or_default()),
                None => String::from(""),
            };
            if old_file.is_not_binary() {
                let old_file = repo.find_blob(old_file.id());
                if old_file.is_err() {
                    continue;
                }
                let old_file = old_file.unwrap();
                old_content = String::from_utf8_lossy(old_file.content()).to_string();
            }
        }
        let delta_type = match delta.status() {
            Delta::Unmodified => "Unmodified",
            Delta::Added => "Added",
            Delta::Deleted => "Deleted",
            Delta::Modified => "Modified",
            Delta::Renamed => "Renamed",
            Delta::Copied => "Copied",
            Delta::Ignored => "Ignored",
            Delta::Untracked => "Untracked",
            Delta::Typechange => "Typechange",
            Delta::Unreadable => "Unreadable",
            Delta::Conflicted => "Conflicted",
        };

        info_list.push(FileDiffInfo {
            old_file_name: old_file_name,
            old_content: old_content,
            new_file_name: new_file_name,
            new_content: new_content,
            delta_type: delta_type.into(),
        });
    }
    println!("{}", serde_json::to_string(&info_list).unwrap());
    return Ok(());
}

pub fn exec(git_path: PathBuf, commit_id: &String) -> Result<(), Error> {
    let repo = Repository::open(git_path);
    if repo.is_err() {
        return Err(repo.err().unwrap());
    }
    let repo = repo.unwrap();

    let commit_id = Oid::from_str(commit_id);
    if commit_id.is_err() {
        return Err(commit_id.err().unwrap());
    }
    let commit_id = commit_id.unwrap();
    let commit = repo.find_commit(commit_id);
    if commit.is_err() {
        return Err(commit.err().unwrap());
    }
    let commit = commit.unwrap();
    let new_tree = commit.tree();
    if new_tree.is_err() {
        return Err(new_tree.err().unwrap());
    }
    let new_tree = new_tree.unwrap();
    let count = commit.parent_count();
    if count == 0 {
        let result = repo.diff_tree_to_tree(None, Some(&new_tree), None);
        if result.is_err() {
            return Err(result.err().unwrap());
        }
        return dump_diff(&repo, result.unwrap());
    } else {
        let parent_commit = commit.parent(0);
        if parent_commit.is_err() {
            return Err(parent_commit.err().unwrap());
        }
        let parent_commit = parent_commit.unwrap();
        let old_tree = parent_commit.tree();
        if old_tree.is_err() {
            return Err(old_tree.err().unwrap());
        }
        let old_tree = old_tree.unwrap();
        let result = repo.diff_tree_to_tree(Some(&old_tree), Some(&new_tree), None);
        if result.is_err() {
            return Err(result.err().unwrap());
        }
        return dump_diff(&repo, result.unwrap());
    }
}
